이번 편에서는 서버에서 응답하는 HTML 파일에 스타일을 적용하는 것을 구현하겠습니다.
React와 함께 자주 쓰이는 스타일링 방식인 CSS-in-JS
방식을 사용하겠습니다.
전통적인 CSS 방식으로 한다면 번들링 과정에서 HTML파일에 CSS파일을 주입해주면 될 것 같습니다.
webpack에 css-loader
설정하면 될 듯 싶습니다. (해보진 않았습니다.)
해당 과정 중 많은 부분을 출처의 실전 리액트 프로그래밍 (이재승 저)책을 참고하여 작성하였습니다. 문제가 될 시 wnsgur6311@gmail.com으로 연락부탁드립니다.
CSS-in-JS
라이브러리 중 저에게 가장 익숙한 styled-components
를 사용하겠습니다.
styled-components 설치 및 환경설정
SSR 환경에서 styled-components
를 사용하기 위해서는 babel-plugin-styled-components
라는 바벨 플러그인이 필요합니다.
이 플러그인은 여러가지 기능이 있지만, 그 중에서 서버 사이드에서 생성한 고유한 클래스이름이 클라이언트 사이드에서 불일치되는 일을 막아줍니다.
-
패키지 설치
npm i styled-components npm i -D babel-plugin-styled-components
-
바벨 설정 -
.babelrc.common.js
const presets = ['@babel/preset-react']; const plugins = ['babel-plugin-styled-components']; module.exports = { presets, plugins };
스타일 구현
간단한 스타일을 구현해줍니다.
-
src/App.js
import React, { useState, useEffect } from 'react'; import Home from "./Home"; import About from "./About"; const Container = styled.div` background-color: #aaaaaa; border: 1px solid blue; ` function App({ page: initialPage }) { const [page, setPage] = useState(initialPage); useEffect(() => { window.onpopstate = event => { setPage(event.state); } },[]); function onChangePage(e) { const newPage = e.target.dataset.page; window.history.pushState(newPage, '', `/${newPage}`); setPage(newPage); }; const PageComponent = page === 'home' ? Home : About; return ( <Container> <button data-page="home" onClick={onChangePage}> Home </button> <button data-page="about" onClick={onChangePage}> About </button> <PageComponent /> </Container> ) } export default App;
HTML에 서버에서 style 넣을 공간 만들기
SPA기반 SSR 구현하기 (feat.React) 4-서버에서 클라이언트로 데이터 전달하기에서
구현한 것 처럼, 서버에서 생성된 스타일을 클라이언트로 전달하기 위한 공간을 __STYLE_FROM_SERVER__
와 같이 만들어줍니다.
-
template/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test-ssr</title> <script type="text/javascript"> window.__INITIAL_DATA__ = __DATA_FROM_SERVER__; </script> __STYLE_FROM_SERVER__ </head> <body> <div id="root"></div> </body> </html>
서버에서 HTML에 스타일 넣기
이제 서버에서 렌더링 과정 중 생성된 스타일을 추출하고, 추출한 스타일을 HTML에 미리 마련한 공간인 __STYLE_FROM_SERVER__
에 넣어줍니다.
-
./src/server.js
import express from 'express'; import fs from 'fs'; import path from 'path'; import url from 'url'; import { renderToString } from 'react-dom/server'; import React from 'react'; import App from './App'; import { ServerStyleSheet } from "styled-components"; const app = express(); const html = fs.readFileSync( path.resolve(__dirname, '../dist/index.html'), 'utf-8', ); app.use('/dist', express.static('dist')); app.get('/favicon.ico', (req, res) => res.sendStatus(204)); app.get('*', (req, res) => { const parsedUrl = url.parse(req.url, true); const page = parsedUrl.pathname.substr(1) || 'home'; const sheet = new ServerStyleSheet(); // (1) const renderString = renderToString(sheet.collectStyles(<App page={page} />)); // (2) const styles = sheet.getStyleTags(); // (3) const initialData = { page }; const result = html .replace('<div id="root"></div>', `<div id="root">${renderString}</div>`) .replace('__DATA_FROM_SERVER__', JSON.stringify(initialData)) .replace('__STYLE_FROM_SERVER__', styles); // (4) res.send(result); }) app.listen(3000);
- (1) 스타일을 추출한 객체를 생성합니다.
- (2)
collectStyles
메소드에 리액트 요소를 입력하여 스타일정보를 수집합니다. 실제 스타일 정보는renderToSting
함수의 호출이 끝나야 수집됩니다. - (3)
getStyledTags
메서드를 호출하여 수집된 스타일정보를 가져옵니다. - (4) HTML에 스타일을 주입합니다.
실행확인
이제 실행시켜 정말 서버로부터 스타일이 잘 담겨오는지 확인하겠습니다.
실행 명령어
npm run build
npm start
결과
우리가 HTML에 미리 준비해놨던 __STYLE_FROM_SERVER__
가 서버에서 생성된 스타일로 잘 주입되어 온 것을 확인할 수 있습니다.
마무리
여기까지 SSR 과정 중 스타일을 적용하는 방법을 구현했습니다.
다음편에서는 api 서버에서 데이터를 가져오고, 가져온 데이터를 컴포넌트에서 사용해 렌더링하는 방법을 알아보겠습니다.